#include "InputDevice.h"
#include "BulletTickListener.h"
#include "OfmsSimpleCar.h"

#include <Ogre.h>
#include <windows.h>
#include <tchar.h>
#include <deque>
#include <OgreBulletDynamicsWorld.h>
#include <OgreBulletDynamicsRigidBody.h>
#include <OgreBulletCollisionsWorld.h>
#include <Shapes/OgreBulletCollisionsStaticPlaneShape.h>
//#include <Shapes/OgreBulletCollisionsBoxShape.h>


namespace OpenFms
{

/** 
 * \class OpenFmsApp
 * \brief Main OpenFMS application class
 */

class OpenFmsApp : public Ogre::FrameListener
{
private:
	Ogre::Root *m_pRoot;                   /**< Pointer to Ogre root object used */
	Ogre::RenderWindow *m_pRenderWindow;   /**< Pointer to Ogre Render Window used */
	Ogre::SceneManager *m_pSceneManager;   /**< Pointer to Ogre Scene Manager used */
	Ogre::Camera *m_pCamera;               /**< Pointer to Ogre Camera used */
	bool loadResources();                 
	InputDevice m_input;                   /**< Input device to use. */
	//FmsVehicle m_vehicle;
	OfmsSimpleCar m_vehicle;               /**< Simple car instance to use, @note This will be remived in future releases. */
	
	// OgreBullet
	OgreBulletDynamics::DynamicsWorld *m_pWorld;  /**< Pointer to the OgreBullet Dynamic World used */

	// OgreBullet containers and freeers
	typedef std::deque<OgreBulletDynamics::RigidBody *> BodyListType;           
	typedef std::deque<OgreBulletCollisions::CollisionShape *> ShapeListType;   
	BodyListType m_bodies;  /**< List of created bodies @note Used to do cleanup */
	ShapeListType m_shapes; /**< List of created shapes @note Used to do cleanup */
	void freeOgreBulletObjects();

	BulletTickListenerList m_ticklist;  /**< The instance of OpenFms::BulletTickListenerList used */

	bool setupInput(const TCHAR *pModuleFileName);
	void setupOgreAndBullet();
	void setupLandscape();

	void cleanupInput();

public:
	OpenFmsApp();
	~OpenFmsApp();
	bool run();
	bool frameStarted( const Ogre::FrameEvent &evt );
	bool frameEnded( const Ogre::FrameEvent &evt );
};

/**
 * Constructor for the OpenFmsApp
 *
 * Just sets all instance variables to NULL
 */
OpenFmsApp::OpenFmsApp() : m_pRoot(NULL), m_pRenderWindow(NULL), m_pWorld(NULL)
{
}

/**
 * Destructor for the OpenFmsApp. 
 *
 * Does some cleanup.
 */
OpenFmsApp::~OpenFmsApp()
{
	cleanupInput();
	freeOgreBulletObjects();
	if( m_pWorld ) delete m_pWorld;
	if( m_pRoot ) OGRE_DELETE(m_pRoot);
}

/**
 * This function is called before a frame is rendered
 *
 * This function is called once for every iteration in the OpenFms 
 * "main loop", and is called just before a frame is rendered.
 *
 * @param evt A structure containing time info (e.g. the time since the last frame was rendered)
 * @return Returning true will keep the program running. Returning false will exit the program
 */
bool OpenFmsApp::frameStarted( const Ogre::FrameEvent &evt )
{
	// If we have closed the window, return false to indicate to the
	// main loop that the program should exit
	if( m_pRenderWindow && m_pRenderWindow->isClosed() ) return false;
	
	// Notify ticklist
	m_ticklist.onFrameStarted( evt );

	// Step the simulation engine
	if(m_pWorld) m_pWorld->stepSimulation( evt.timeSinceLastFrame, 10 );

	// Point the camera in the direction of the flying object
	if( m_pSceneManager && m_pCamera ) {
		Ogre::Node *pNode = m_vehicle.getSceneNode();
		if( pNode ) {
			Ogre::Vector3 pos = pNode->getPosition();
			m_pCamera->lookAt(pos);
		}
	}
	
	return true;
}

/**
 * This function is called after a frame is rendered
 *
 * This function is called once for every iteration in the OpenFms 
 * "main loop", and is called just after a frame has been rendered. 
 *
 * @param evt A structure containing time info (e.g. the time since the last frame was rendered)
 * @return Returning true will keep the program running. Returning false will exit the program
 */
bool OpenFmsApp::frameEnded( const Ogre::FrameEvent &evt )
{
	// Sleep for a litte while to offload the processor
	// todo: remove this when the program becomes a litte more complex
	Sleep(5);

	return true;
}

/**
 * Loads the readources specified th the "resources.cfg" file
 *
 * @return Returns true on success, false on fail.
 */
bool OpenFmsApp::loadResources()
{
	Ogre::ConfigFile cf; cf.load("resources.cfg");
	Ogre::ConfigFile::SectionIterator seci = cf.getSectionIterator(); 
	while(seci.hasMoreElements())
	{
		Ogre::String secName = seci.peekNextKey();
		Ogre::ConfigFile::SettingsMultiMap *settings = seci.getNext();
		for( Ogre::ConfigFile::SettingsMultiMap::iterator i=settings->begin(); i != settings->end(); ++i )
		{
			Ogre::String typeName = i->first;
			Ogre::String archName = i->second;
			Ogre::ResourceGroupManager::getSingleton().addResourceLocation( archName, typeName, secName );
		}
	}

	Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups();
	return true;
}

/**
 * Runs the application
 *
 * @return Returns false if the application was exited due to an error. Returns true otherwise.
 */
bool OpenFmsApp::run()
{
	if( setupInput(_T("EskyInput.dll")) == false ) {
		if( setupInput(_T("KeyboardInput.dll")) == false ) {
			MessageBox(NULL, _T("Unable to find neither Esky-constroller or Keyboard."), _T("Aborting"), MB_OK|MB_ICONERROR );
			return false;
		}
		MessageBox(NULL,
			_T("Channel 0: Arrows right/left.\n")
			_T("Channel 1: Arrows up/down.\n")
			_T("Channel 2: Letters W/S.\n")
			_T("Channel 3: Letters D/A."), 
			_T("Using keyboard input"), MB_OK|MB_ICONINFORMATION );
	}

	m_pRoot = new Ogre::Root();
	if( m_pRoot == NULL ) return false;
	if(m_pRoot->showConfigDialog())
	{
		setupOgreAndBullet();
		setupLandscape();
		//setupAircraft();

		//m_vehicle.load(Ogre::String("Aircraft"), m_pWorld, m_pSceneManager );
		m_vehicle.load(Ogre::String("SimpleCar"), m_pWorld, m_pSceneManager );
		m_vehicle.attachInput(&m_input);
		m_ticklist.addListener(&m_vehicle);

		// do the rendering
		m_pRoot->addFrameListener(this);
		m_pRoot->startRendering();
	}

	// cleanup and exit
	m_ticklist.removeAllListeners(false);
	m_vehicle.unload();
	return true;
}

/**
 * Setup the input device
 */
bool OpenFmsApp::setupInput(const TCHAR *pModuleFileName)
{
	if( m_input.loadLibrary(pModuleFileName)== false ) {
	    MessageBox(NULL, pModuleFileName, _T("Unable to load library"), MB_OK|MB_ICONERROR );
		return false;
	}
	if( m_input.createDevice() == false ) {
		MessageBox(NULL, _T("Unable to instanciate device"), pModuleFileName, MB_OK|MB_ICONERROR );
		return false;
	}
	return true;
}


/**
 * Frees resourses used by the input device
 */
void OpenFmsApp::cleanupInput()
{
	m_input.destroyDevice();
	m_input.unloadLibrary();
}

/**
 * Setup the simulator
 *
 * For now: input via EskyInput.dll only. TODO: more inputs
 */
/*bool OpenFmsApp::setupSimulator(const TCHAR *pModuleFileName, const TCHAR *pModelFileName)
{
	cleanupSimulator(); // remove if loaded.

	// try to load
	if( m_simulator.load(pModuleFileName, pModelFileName ) == false)
	{
		MessageBox(NULL, m_simulator.getLastErrorMessage(), _T("Simulator load error"), MB_OK | MB_ICONERROR );
		return false;
	}
	return true;
}*/

/**
 * Frees resourses used by the model simulator
 */
/*void OpenFmsApp::cleanupSimulator()
{
	m_simulator.destroy();
}*/

/**
 * Setup Ogre, Bullet and OgreBullet 
 *
 * Does some fundamental setup, like creating the main window, 
 * setting world size, creates a camera, etc.
 */
void OpenFmsApp::setupOgreAndBullet()
{
	// Create Window
	m_pRenderWindow = m_pRoot->initialise(true);

	// Create SceneManager
	m_pSceneManager = m_pRoot->createSceneManager("DefaultSceneManager");

	// create physics world
	const Ogre::AxisAlignedBox bounds( -10000, -10000, -10000, 10000, 10000, 10000 );
	const Ogre::Vector3 gravity( 0, 0, 9.8f );
	m_pWorld = new OgreBulletDynamics::DynamicsWorld( m_pSceneManager, bounds, gravity );

	// Create Camera
	m_pCamera = m_pSceneManager->createCamera( "TheCamera" );
	m_pCamera->setPosition( -5, 0, -3 );
	m_pCamera->setFixedYawAxis( true, -Ogre::Vector3::UNIT_Z );
	m_pCamera->lookAt(100, 0, 0 );
	m_pCamera->setNearClipDistance(1);
	m_pCamera->setFarClipDistance(1000);

	// Setup viewport
	Ogre::Viewport *vp = m_pRenderWindow->addViewport( m_pCamera );
	vp->setBackgroundColour(Ogre::ColourValue(.3f, .3f, 1.f));
	m_pCamera->setAspectRatio( Ogre::Real(vp->getActualWidth())/Ogre::Real(vp->getActualHeight()) );

	// Load resources
	this->loadResources();
}

/**
 * Sets up the landscape
 *
 * This funciton instanciates the resources needed for the landscape, and
 * adds then to both the visual and physics engines.
 */
void OpenFmsApp::setupLandscape()
{

	// Add ground (part 1 of 2 - visual)
	const Ogre::Vector3 groundNormal( -Ogre::Vector3::UNIT_Z );
	Ogre::MeshManager::getSingleton().createPlane( "groundMesh", "Landscape",
		//Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, 
		Ogre::Plane( groundNormal, 0 ), 1000, 1000, 10, 10, true, 1, 500, 500, Ogre::Vector3::UNIT_X);
	Ogre::Entity *pGround = m_pSceneManager->createEntity( "ground", "groundMesh" );
	pGround->setMaterialName("grass", "Landscape" );
	Ogre::SceneNode *pNode = m_pSceneManager->getRootSceneNode()->createChildSceneNode("groundNode");
	pNode->attachObject( pGround );
	
	// Add ground (part 2 of 2 - physical)
	const float restitution = 0.6f;
	const float friction = 0.8f;
	OgreBulletCollisions::CollisionShape *pGroundShape = new OgreBulletCollisions::StaticPlaneCollisionShape( groundNormal, 0 );
	OgreBulletDynamics::RigidBody *pGroundBody = new OgreBulletDynamics::RigidBody( "BasePlane", m_pWorld );
	pGroundBody->setStaticShape( pGroundShape, restitution, friction );

	m_shapes.push_back(pGroundShape);
	m_bodies.push_back(pGroundBody);

	m_ticklist.attach( m_pWorld->getBulletDynamicsWorld() );

}

/**
 * Sets up the aircraft
 *
 * This funciton instanciates the resources needed for the aircraft, and
 * adds then to both the visual and physics engines.
 */
/*void OpenFmsApp::setupAircraft()
{
	// Add cube (part 1 of 2 - visual)
	Ogre::Entity *pBox = m_pSceneManager->createEntity("aircraftEntity", "Pitts.mesh");
	pBox->setMaterialName("bumpy");
	Ogre::SceneNode *pBoxNode = m_pSceneManager->getRootSceneNode()->createChildSceneNode("aircraftNode");
	pBoxNode->attachObject( pBox );

	Ogre::AxisAlignedBox boxbox = pBox->getBoundingBox();
	OgreBulletCollisions::BoxCollisionShape *pBoxShape = new OgreBulletCollisions::BoxCollisionShape(.96f * boxbox.getSize() / 2.0f);
	OgreBulletDynamics::RigidBody *pBoxBody = new OgreBulletDynamics::RigidBody( "aircraftBody", m_pWorld );

	ModelSimulator_inertias_t inertias = { sizeof(ModelSimulator_inertias_t) };
	m_simulator.getInertias( inertias );

	const float mass = inertias.mass;
	const Ogre::Vector3 boxPosition(10,0,-.6f);
	const Ogre::Quaternion boxOrientation(0,0,0,1);
	const float restitution = .6f;
	const float friction = .6f;
	pBoxBody->setShape( pBoxNode, pBoxShape, restitution, friction, mass, boxPosition, boxOrientation );

	m_shapes.push_back(pBoxShape);
	m_bodies.push_back(pBoxBody);

	// create physics callback
	m_ticklist.addListener( new TickListener(pBoxBody->getBulletRigidBody(), &m_input, &m_simulator));
	pBoxBody->getBulletRigidBody()->setActivationState(DISABLE_DEACTIVATION);

}*/

/**
 * This function frees all the resources allocated by OgreBullet
 *
 * Note: Corresponing functions for Bullet and Ogre resources are not needed, 
 * since OgreBullet will free the allocateds Bullet resources, and Ogre will
 * free it's own resources automatically.
 */
void OpenFmsApp::freeOgreBulletObjects()
{
	// Free the objects in the bodies list
	for( BodyListType::iterator iter = m_bodies.begin(); iter != m_bodies.end(); ++iter ) {
		delete *iter;
	}
	m_bodies.clear();

	// Free the objects in the shapes list
	for( ShapeListType::iterator iter = m_shapes.begin(); iter != m_shapes.end(); ++iter ) {
		delete *iter;
	}
	m_shapes.clear();
}


}// namespace OpenFms

using namespace OpenFms;

/**
 * WinMain enty point
 *
 * Just creates an OpenFmsApp object and calles it's run() function
 */
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow )
{
	OpenFmsApp app;
	try
	{
		app.run();
	}
	catch (Ogre::Exception &e)
	{
		MessageBoxA(NULL, e.getFullDescription().c_str(), "Ogre exception", MB_OK|MB_ICONERROR);
		//MessageBox(NULL, _T("Ogre exception"), _T("Ogre exception"), MB_OK|MB_ICONERROR);
		return 1;
	}
	return 0;
}



